package game.game;

import com.crunch.math.Vector2f;
import com.crunch.physics.Segment;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import scenery.BackgroundLayer;
import scenery.TileLayer;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;

/**
 * Definition of a room.
 */
public class RoomDefinition {
	private static final int DEFAULT_ROOM_WIDTH     = 16 * 100;
	private static final int DEFAULT_ROOM_HEIGHT    = 16 * 100;

	public RoomDefinition() {
		roomWidth = DEFAULT_ROOM_WIDTH;
		roomHeight = DEFAULT_ROOM_HEIGHT;

		backgroundLayers = new ArrayList<BackgroundLayerDefinition>();
		tileLayers = new ArrayList<TileLayerDefinition>();
		staticColliders = new ArrayList<Segment>();
		actors = new ArrayList<ActorDefinition>();
	}

	public void save(Path path)
			throws IOException {
		Writer writer = null;
		try {
			writer = Files.newBufferedWriter(path, Charset.defaultCharset());
			JSONObject rootObj = new JSONObject();

			rootObj.put("roomWidth" , roomWidth);
			rootObj.put("roomHeight", roomHeight);

			JSONArray backgroundLayersArr = new JSONArray();
			rootObj.put("backgroundLayers", backgroundLayersArr);
			for (BackgroundLayerDefinition backgroundLayer : backgroundLayers) {
				JSONObject backgroundLayerObj = new JSONObject();
				backgroundLayersArr.put(backgroundLayerObj);

				backgroundLayerObj.put("layer", backgroundLayer.getBackgroundLayer().save());
				backgroundLayerObj.put("texture", backgroundLayer.getTexture());
			}

			JSONArray tileLayersArr = new JSONArray();
			rootObj.put("tileLayers", tileLayersArr);
			for (TileLayerDefinition tileLayer : tileLayers) {
				JSONObject tileLayerObj = new JSONObject();
				tileLayersArr.put(tileLayerObj);

				tileLayerObj.put("layer", tileLayer.getTileLayer().save());
				tileLayerObj.put("texture", tileLayer.getTexture());
			}

			JSONArray staticCollidersArr = new JSONArray();
			rootObj.put("staticColliders", staticCollidersArr);
			for (Segment segment : staticColliders) {
				JSONArray staticColliderArr = new JSONArray();
				Vector2f pA = segment.getA();
				Vector2f pB = segment.getB();
				JSONArray aArr = new JSONArray().put(pA.x()).put(pA.y());
				JSONArray bArr = new JSONArray().put(pB.x()).put(pB.y());
				staticColliderArr.put(aArr).put(bArr);
				staticCollidersArr.put(staticColliderArr);
			}

			JSONArray gameActorsArr = new JSONArray();
			rootObj.put("gameActors", gameActorsArr);
			for (ActorDefinition actor : actors) {
				JSONObject gameActorObj = new JSONObject();
				gameActorsArr.put(gameActorObj);

				gameActorObj.put("class", actor.getClassName());
				gameActorObj.put("state", actor.getState());
			}

			rootObj.write(writer);
		} catch (JSONException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to write " + path);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {}
			}
		}
	}

	public void load(Path path)
			throws IOException {
		backgroundLayers.clear();
		tileLayers.clear();
		staticColliders.clear();
		actors.clear();

		Reader reader = null;
		try {
			reader = Files.newBufferedReader(path, Charset.defaultCharset());
			JSONObject rootObj = new JSONObject(new JSONTokener(reader));

			roomWidth = rootObj.getInt("roomWidth");
			roomHeight = rootObj.getInt("roomHeight");

			JSONArray backgroundLayersArr = rootObj.getJSONArray("backgroundLayers");
			for (int i = 0; i < backgroundLayersArr.length(); ++i) {
				JSONObject backgroundLayerObj = backgroundLayersArr.getJSONObject(i);

				BackgroundLayerDefinition backgroundLayer = new BackgroundLayerDefinition();
				backgroundLayer.getBackgroundLayer().load(backgroundLayerObj.getJSONObject("layer"));
				backgroundLayer.setTexture(backgroundLayerObj.getString("texture"));
				backgroundLayers.add(backgroundLayer);
			}

			JSONArray tileLayersArr = rootObj.getJSONArray("tileLayers");
			for (int i = 0; i < tileLayersArr.length(); ++i) {
				JSONObject tileLayerObj = tileLayersArr.getJSONObject(i);

				TileLayerDefinition tileLayer = new TileLayerDefinition();
				tileLayer.getTileLayer().load(tileLayerObj.getJSONObject("layer"));
				tileLayer.setTexture(tileLayerObj.getString("texture"));
				tileLayers.add(tileLayer);
			}

			JSONArray staticCollidersArr = rootObj.getJSONArray("staticColliders");
			for (int i = 0; i < staticCollidersArr.length(); ++i) {
				JSONArray staticColliderArr = staticCollidersArr.getJSONArray(i);

				JSONArray aArr = staticColliderArr.getJSONArray(0);
				JSONArray bArr = staticColliderArr.getJSONArray(1);
				Segment segment = new Segment(
						new Vector2f((float) aArr.getDouble(0), (float) aArr.getDouble(1)),
						new Vector2f((float) bArr.getDouble(0), (float) bArr.getDouble(1)));
				staticColliders.add(segment);
			}

			JSONArray gameActorsArr = rootObj.getJSONArray("gameActors");
			for (int i = 0; i < gameActorsArr.length(); ++i) {
				JSONObject gameActorObj = gameActorsArr.getJSONObject(i);

				ActorDefinition actor = new ActorDefinition();
				actor.setClassName(gameActorObj.getString("class"));
				actor.setState(gameActorObj.getJSONObject("state"));
				actors.add(actor);
			}
		} catch (JSONException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to read " + path);
		} catch (IOException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to read " + path);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {}
			}
		}
	}

	public static class BackgroundLayerDefinition {
		public BackgroundLayerDefinition() {
			backgroundLayer = new BackgroundLayer();
		}

		public BackgroundLayer getBackgroundLayer() {
			return backgroundLayer;
		}

		public String getTexture() {
			return texture;
		}

		public void setTexture(String texture) {
			this.texture = texture;
		}

		private BackgroundLayer backgroundLayer;
		private String texture;
	}

	public static class TileLayerDefinition {
		public TileLayerDefinition() {
			tileLayer = new TileLayer();
		}

		public TileLayer getTileLayer() {
			return tileLayer;
		}

		public String getTexture() {
			return texture;
		}

		public void setTexture(String texture) {
			this.texture = texture;
		}

		private TileLayer tileLayer;
		private String texture;
	}

	public static class ActorDefinition {
		public ActorDefinition() {
		}

		public String getClassName() {
			return className;
		}

		public void setClassName(String className) {
			this.className = className;
		}

		public JSONObject getState() {
			return state;
		}

		public void setState(JSONObject state) {
			this.state = state;
		}

		private String className;
		private JSONObject state;
	}

	public int getRoomWidth() {
		return roomWidth;
	}

	public int getRoomHeight() {
		return roomHeight;
	}

	public void setRoomSize(int roomWidth, int roomHeight) {
		this.roomWidth = roomWidth;
		this.roomHeight = roomHeight;
	}

	public int getBackgroundLayerCount() {
		return backgroundLayers.size();
	}

	public BackgroundLayerDefinition getBackgroundLayer(int i) {
		return backgroundLayers.get(i);
	}

	public void addBackgroundLayer(int i, BackgroundLayerDefinition backgroundLayer) {
		backgroundLayers.add(i, backgroundLayer);
	}

	public void removeBackgroundLayer(int i) {
		backgroundLayers.remove(i);
	}

	public int getTileLayerCount() {
		return tileLayers.size();
	}

	public TileLayerDefinition getTileLayer(int i) {
		return tileLayers.get(i);
	}

	public void addTileLayer(int i, TileLayerDefinition tileLayer) {
		tileLayers.add(i, tileLayer);
	}

	public void removeTileLayer(int i) {
		tileLayers.remove(i);
	}

	public int getStaticColliderCount() {
		return staticColliders.size();
	}

	public Segment getStaticCollider(int i) {
		return staticColliders.get(i);
	}

	public void addStaticCollider(int i, Segment segment) {
		staticColliders.add(i, segment);
	}

	public void removeStaticCollider(int i) {
		staticColliders.remove(i);
	}

	public int getActorCount() {
		return actors.size();
	}

	public ActorDefinition getActor(int i) {
		return actors.get(i);
	}

	public void addActor(int i, ActorDefinition actor) {
		actors.add(i, actor);
	}

	public void removeActor(int i) {
		actors.remove(i);
	}

	// room size
	private int roomWidth, roomHeight;

	// background layers
	private ArrayList<BackgroundLayerDefinition> backgroundLayers;

	// tile layers
	private ArrayList<TileLayerDefinition> tileLayers;

	// list of static colliders
	private ArrayList<Segment> staticColliders;

	// list of actor definitions
	private ArrayList<ActorDefinition> actors;
}
